package shark.tenant.interceptor;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import shark.tenant.holder.TenantHolder;
import shark.tenant.model.SysTenant;
import shark.tenant.service.TenantSchemaService;

import java.lang.reflect.Field;
import java.util.List;

@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class TenantInterceptor implements Interceptor, ApplicationContextAware {

    private TenantSchemaService tenantSchemaService;

    private TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement)invocation.getArgs()[0];
        Object parameterObject = null;
        if(invocation.getArgs().length > 1){
            parameterObject = invocation.getArgs()[1];
        }
        BoundSql boundSql = ms.getBoundSql(parameterObject);

        String newSql = replace(boundSql.getSql());
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, newSql);

        MappedStatement.Builder build = new MappedStatement.Builder(
                ms.getConfiguration(),
                ms.getId(),
                new CustomSqlSource(boundSql),
                ms.getSqlCommandType()
        );
        build.resource(ms.getResource());
        build.fetchSize(ms.getFetchSize());
        build.statementType(ms.getStatementType());
        build.keyGenerator(ms.getKeyGenerator());
        build.timeout(ms.getTimeout());
        build.parameterMap(ms.getParameterMap());
        build.resultMaps(ms.getResultMaps());
        build.cache(ms.getCache());

        MappedStatement newStmt = build.build();
        //替换原来的MappedStatement
        invocation.getArgs()[0] = newStmt;

        return invocation.proceed();
    }

    private String replace(String sql) throws JSQLParserException {
        Statement stmt = CCJSqlParserUtil.parse(sql);

        boolean isTenant = isTenantTable(stmt);
        if (!isTenant) {
            return sql;
        }

        SysTenant tenant = TenantHolder.get();
        String schemeName = String.format("%s", tenant.getSchemaName());

        List<String> tables = tablesNamesFinder.getTableList(stmt);

        for (String name : tables) {
            sql = sql.replace(name,schemeName+"."+name);
        }

        return sql;
    }

    private boolean isTenantTable(Statement stmt) {
        List<String> tables = tablesNamesFinder.getTableList(stmt);
        List<String> tenantList = tenantSchemaService.getTenantSchemaList();
        // 判断是否为租户表
        for (String table : tables) {
            for (String tenantNm : tenantList) {
                if (table.equals(tenantNm)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.tenantSchemaService = applicationContext.getBean(TenantSchemaService.class);
    }

    public static class CustomSqlSource implements SqlSource{

        private BoundSql boundSql;

        protected CustomSqlSource(BoundSql boundSql){
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object o) {
            return boundSql;
        }
    }

}
